package com.example.java_chatroom.api;

import com.example.java_chatroom.component.OnlineUserManager;
import com.example.java_chatroom.constant.BaseConst;
import com.example.java_chatroom.model.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.List;

/**
 * webSocket主要功能API
 */
@Slf4j
@Component
public class WebSocketAPI extends TextWebSocketHandler {

    /**
     * 上线用户管理
     */
    @Autowired
    private OnlineUserManager onlineUserManager;

    /**
     * 消息会话表mapper
     */
    @Autowired
    private MessageSessionMapper messageSessionMapper;

    /**
     * 消息表mapper
     */
    @Autowired
    private MessageMapper messageMapper;

    /**
     * jackson 提供的一个工具类
     */
    private ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 在 webSocket 连接之后执行的方法
     * @param session       连接的创建的 WebSocketSession
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        log.info("[WebSocketAPI] 连接成功!， session为：{}", session);
        User user = (User) session.getAttributes().get("user");
        // 如果 session 中没有user，则说明该会话有问题，则直接返回
        if (null == user) {
            log.error("webSocketSession获取user对象为空，webSocketSession为：{}", session);
            return;
        }
        // 把这个键值对给存起来
        onlineUserManager.online(user.getUserId(), session);
    }

    /**
     * 当收到消息时会执行这个方法
     * @param session       当前的 webSocketSession
     * @param message       收到的消息
     * @throws Exception        异常
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        log.info("[WebSocketAPI] 收到消息！消息为： {}", message.toString());
        // 1. 先获取到当前用户的信息. 后续要进行消息转发啥的.
        User user = (User) session.getAttributes().get("user");
        // 当前用户未在线，当前发消息的用户
        if (null == user) {
            log.info("[WebSocketAPI] user == null! 未登录用户, 无法进行消息转发");
            // 直接返回，不做任何操作
            return;
        }
        // 2. 针对请求进行解析. 把 json 格式的字符串, 转成一个 Java 中的对象
        MessageRequest req = objectMapper.readValue(message.getPayload(), MessageRequest.class);
        if (BaseConst.ONLINE_CHAT_MESSAGE_TYPE.equals(req.getType())) {
            // 就进行消息转发
            transferMessage(user, req);
        } else {
            log.error("[WebSocketAPI] req.type 有误! req.type = {}", req.getType());
        }
    }

    /**
     * 通过这个方法来完成消息实际的转发工作.
     * 第一个参数就表示这个要转发的消息, 是从谁那来的.
     * @param fromUser      发送者是谁
     * @param req           消息会话请求模型
     * @throws IOException      异常
     */
    private void transferMessage(User fromUser, MessageRequest req) throws IOException {
        // 1. 先构造一个待转发的响应对象. MessageResponse
        MessageResponse resp = new MessageResponse();
        // 设置消息类型
        resp.setType(BaseConst.ONLINE_CHAT_MESSAGE_TYPE); // 这里不设置也行, 默认也就是 message
        // 设置消息发送者id
        resp.setFromId(fromUser.getUserId());
        // 设置消息发送者名称
        resp.setFromName(fromUser.getUsername());
        // 设置会话id，这个是微信聊天的那个会话id
        resp.setSessionId(req.getSessionId());
        // 设置发消息的内容
        resp.setContent(req.getContent());
        // 把这个 java 对象转成 json 格式字符串
        String respJson = objectMapper.writeValueAsString(resp);
        System.out.println("[transferMessage] respJson: " + respJson);

        // 2. 根据请求中的 sessionId, 获取到这个 MessageSession 里都有哪些用户. 通过查询数据库就能够知道了.
        // 在数据库中查询某个会话id都对应哪些人
        List<Friend> friends = messageSessionMapper.getFriendsBySessionId(req.getSessionId(), fromUser.getUserId());
        // 此处注意!!! 上述数据库查询, 会把当前发消息的用户给排除掉. 而最终转发的时候, 则需要也把发送消息的人也发一次.
        // 把当前用户也添加到上述 List 里面
        Friend myself = new Friend();
        // 设置自己的id
        myself.setFriendId(fromUser.getUserId());
        // 设置自己的姓名
        myself.setFriendName(fromUser.getUsername());
        friends.add(myself);

        // 3. 循环遍历上述的这个列表, 给列表中的每个用户都发一份响应消息
        //    注意: 这里除了给查询到的好友们发, 也要给自己也发一个. 方便实现在自己的客户端上显示自己发送的消息.
        //    注意: 一个会话中, 可能有多个用户(群聊). 虽然客户端是没有支持群聊的(前端写起来相对麻烦), 后端无论是 API 还是 数据库
        //          都是支持群聊的. 此处的转发逻辑也一样让它支持群聊.
        for (Friend friend : friends) {
            // 知道了每个用户的 userId, 进一步的查询刚才准备好的 OnlineUserManager, 就知道了对应的 WebSocketSession
            // 从而进行发送消息
            // 在线hash表中查询当前用户是否在线
            WebSocketSession webSocketSession = onlineUserManager.getSession(friend.getFriendId());
            if (webSocketSession == null) {
                // 如果该用户未在线, 则不发送.
                continue;
            }
            webSocketSession.sendMessage(new TextMessage(respJson));
        }

        // 4. 转发的消息, 还需要放到数据库里. 后续用户如果下线之后, 重新上线, 还可以通过历史消息的方式拿到之前的消息.
        //    需要往 message 表中写入一条记录.
        // 将消息持久化
        Message message = new Message();
        // 消息发送者
        message.setFromId(fromUser.getUserId());
        // 消息会话id
        message.setSessionId(req.getSessionId());
        // 消息内容
        message.setContent(req.getContent());
        // 像自增主键, 还有时间这样的属性, 都可以让 SQL 在数据库中生成
        messageMapper.add(message);
    }

    /**
     * 当webSocket发送消息出现异常执行的方法
     * @param session           webSocket会话
     * @param exception         异常
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) {
        log.error("[WebSocketAPI] 连接异常! session信息为：{}，异常信息为：{}", session.toString(),  exception.toString());
        doOffline(session);
    }

    /**
     * webSocketSession连接关闭时执行的方法
     * @param session       webSocket会话
     * @param status        关闭状态
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        log.info("[WebSocketAPI] 连接关闭! session={}", session.toString());
        doOffline(session);
    }

    /**
     * webSocketSession 处理下线
     * @param session       WebSocketSession
     */
    private void doOffline(WebSocketSession session) {
        User user = (User) session.getAttributes().get("user");
        if (user == null) {
            return;
        }
        onlineUserManager.offline(user.getUserId(), session);
    }
}
